# Copyright (c) HySoP 2011-2024
#
# This file is part of HySoP software.
# See "https://particle_methods.gricad-pages.univ-grenoble-alpes.fr/hysop-doc/"
# for further info.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from abc import ABCMeta
from hysop.tools.htypes import check_instance, first_not_None
from hysop.tools.transposition_states import TranspositionState
from hysop.tools.numpywrappers import npw
from hysop.constants import MemoryOrdering
[docs]
class NodeRequirements:
__slots__ = ("_node",)
def __init__(self, node):
from hysop.core.graph.computational_node import ComputationalGraphNode
check_instance(node, ComputationalGraphNode)
self._node = node
[docs]
def check_and_update_reqs(self, field_requirements):
"""Check and possibly update field requirements against global node requirements."""
pass
[docs]
class OperatorRequirements(NodeRequirements):
__slots__ = (
"_enforce_unique_transposition_state",
"_enforce_unique_topology_shape",
"_enforce_unique_memory_order",
"_enforce_unique_ghosts",
)
def __init__(
self,
operator,
enforce_unique_transposition_state=None,
enforce_unique_topology_shape=None,
enforce_unique_memory_order=None,
enforce_unique_ghosts=None,
):
from hysop.core.graph.computational_operator import ComputationalGraphOperator
check_instance(operator, ComputationalGraphOperator)
super().__init__(node=operator)
enforce_unique_transposition_state = first_not_None(
enforce_unique_transposition_state, True
)
enforce_unique_topology_shape = first_not_None(
enforce_unique_topology_shape, True
)
enforce_unique_memory_order = first_not_None(enforce_unique_memory_order, True)
enforce_unique_ghosts = first_not_None(enforce_unique_ghosts, False)
self.enforce_unique_transposition_state = enforce_unique_transposition_state
self.enforce_unique_topology_shape = enforce_unique_topology_shape
self.enforce_unique_memory_order = enforce_unique_memory_order
self.enforce_unique_ghosts = enforce_unique_ghosts
@property
def operator(self):
return self._node
def _set_enforce_unique_transposition_state(self, val):
check_instance(val, bool)
self._enforce_unique_transposition_state = val
def _get_enforce_unique_transposition_state(self):
return self._enforce_unique_transposition_state
enforce_unique_transposition_state = property(
_get_enforce_unique_transposition_state, _set_enforce_unique_transposition_state
)
def _set_enforce_unique_topology_shape(self, val):
check_instance(val, bool)
self._enforce_unique_topology_shape = val
def _get_enforce_unique_topology_shape(self):
return self._enforce_unique_topology_shape
enforce_unique_topology_shape = property(
_get_enforce_unique_topology_shape, _set_enforce_unique_topology_shape
)
def _set_enforce_unique_memory_order(self, val):
check_instance(val, bool)
self._enforce_unique_memory_order = val
def _get_enforce_unique_memory_order(self):
return self._enforce_unique_memory_order
enforce_unique_memory_order = property(
_get_enforce_unique_memory_order, _set_enforce_unique_memory_order
)
def _set_enforce_unique_ghosts(self, val):
check_instance(val, bool)
self._enforce_unique_ghosts = val
def _get_enforce_unique_ghosts(self):
return self._enforce_unique_ghosts
enforce_unique_ghosts = property(
_get_enforce_unique_ghosts, _set_enforce_unique_ghosts
)
[docs]
def check_and_update_reqs(self, field_requirements):
"""Check and possibly update field requirements against global node requirements."""
if field_requirements is None:
raise RuntimeError
axes = set()
memory_order, can_split, min_ghosts, max_ghosts = None, None, None, None
field_names = ""
for is_input, freqs in field_requirements.iter_requirements():
if freqs is None:
continue
(field, td, req) = freqs
if self.enforce_unique_transposition_state:
if axes:
if not axes.intersection(set(req.axes)):
msg = "::GLOBAL OPERATOR REQUIREMENTS ERROR::\n"
msg += "Previous axes: \n {} required by fields {}\nare incompatible "
msg += (
"with axes requirements \n {} enforced by {} field {}.\n"
)
msg = msg.format(
tuple(axes),
field_names,
tuple(req.axes),
"input" if is_input else "output",
field.name,
)
raise RuntimeError(msg)
axes = axes.intersection(set(req.axes))
elif req.axes:
axes = axes.union(set(req.axes))
if self.enforce_unique_memory_order:
assert req.memory_order is not None
if req.memory_order != MemoryOrdering.ANY:
if memory_order is not None:
if memory_order != req.memory_order:
msg = "::GLOBAL OPERATOR REQUIREMENTS ERROR::\n"
msg += "Previous memory order: \n {} required by fields {}\nare incompatible "
msg += "with memory order requirements \n {} enforced by {} field {}.\n"
msg = msg.format(
memory_order,
field_names,
req.memory_order,
"input" if is_input else "output",
field.name,
)
raise RuntimeError(msg)
else:
memory_order = req.memory_order
if self.enforce_unique_topology_shape:
assert req.can_split is not None
if can_split is not None:
if npw.sum(can_split * req.can_split) == 0:
if all(can_split == req.can_split):
pass
else:
msg = "::GLOBAL OPERATOR REQUIREMENTS ERROR::\n"
msg += "Previous cartesian split directions: \n {} required by fields {}\nare incompatible "
msg += "with cartesian split directions requirements \n {} enforced by {} field {}."
msg = msg.format(
can_split,
field_names,
req.can_split,
"input" if is_input else "output",
field.name,
)
msg += "\nDomain cannot be splitted accross multiple processes.\n"
raise RuntimeError(msg)
else:
can_split *= req.can_split
else:
can_split = req.can_split.copy()
if self.enforce_unique_ghosts:
ming, maxg = req.min_ghosts, req.max_ghosts
if min_ghosts is not None:
msg = "::GLOBAL OPERATOR REQUIREMENTS ERROR::\n"
if any(maxg < min_ghosts):
msg += "Previous cartesian min_ghosts: \n {} required by fields {}\nare incompatible "
msg += "with cartesian max_ghosts requirements \n {} enforced by {} field {}.\n"
msg = msg.format(
min_ghosts,
field_names,
maxg,
"input" if is_input else "output",
field.name,
)
raise RuntimeError(msg)
elif any(ming > max_ghosts):
msg += "Previous cartesian max_ghosts: \n {} required by fields {}\nare incompatible "
msg += "with cartesian min_ghosts requirements \n {} enforced by {} field {}.\n"
msg = msg.format(
max_ghosts,
field_names,
ming,
"input" if is_input else "output",
field.name,
)
raise RuntimeError(msg)
else:
min_ghosts = npw.maximum(min_ghosts, ming)
max_ghosts = npw.minimum(max_ghosts, maxg)
else:
min_ghosts = ming
max_ghosts = maxg
assert all(max_ghosts >= min_ghosts)
field_names += field.name + ", "
axes = tuple(axes)
has_single_input = len(field_requirements._input_field_requirements) <= 1
# enforce global operator requirements onto field requirements
for is_input, freqs in field_requirements.iter_requirements():
if freqs is None:
continue
(field, td, req) = freqs
# enforce transposition axes
if self.enforce_unique_transposition_state:
if axes:
req.axes = (axes[0],)
elif not has_single_input:
req.axes = (TranspositionState[field.dim].default_axes(),)
# enforce memory order
if self.enforce_unique_memory_order:
if memory_order is not None:
req.memory_order = memory_order
elif not has_single_input:
req.memory_order = MemoryOrdering.C_CONTIGUOUS
# enforce topology shape (indirectly by enforcing split directions)
if self.enforce_unique_topology_shape:
assert can_split is not None
if sum(can_split) > 1:
i = 0
while can_split[i] == 0:
i += 1
can_split[i + 1 :] = 0
req.can_split = can_split
# enforce ghosts by setting min and max to the same
if self.enforce_unique_ghosts:
req.min_ghosts = min_ghosts
req.max_ghosts = min_ghosts